﻿using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WebChat.Models;

namespace WebChat.Repositories
{
    public class UserRepository : IRepository<User>
    {
        private const int SessionKeyLen = 50;
        private const string SessionKeyChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
        private static Random rand = new Random();

        private IDbSet<User> dbSet;
        private DbContext context;

        public UserRepository(DbContext context)
        {
            if (context == null)
            {
                throw new ArgumentException("An instance of DbContext is required to use this repository.", "context");
            }

            this.context = context;
            this.dbSet = this.context.Set<User>();
        }

        public User Add(User entity)
        {
            var existingUser = this.dbSet.FirstOrDefault(x => x.Username.ToLower() == entity.Username.ToLower());
            if (existingUser != null)
            {
                throw new ArgumentException("Username already exists in the database.");
            }

            this.dbSet.Add(entity);
            this.context.SaveChanges();
            return entity;
        }

        public IQueryable<User> All()
        {
            return this.dbSet;
        }

        public User Get(int id)
        {
            var user = this.dbSet.Find(id);
            return user;
        }

        public string Login(User user)
        {

            var excistingUser = this.dbSet.FirstOrDefault(x => x.Username == user.Username && x.Password == user.Password);
            if (excistingUser == null)
            {
                throw new ArgumentException("Invalid username or password");
            }

            var sessionKey = GenerateSessionKey((int)excistingUser.UserId);
            excistingUser.SessionKey = sessionKey;            
            context.SaveChanges();
            return sessionKey;            
        }

        public void Logout(string sessionKey)
        {
            ValidateSessionKey(sessionKey);
            var user = this.dbSet.FirstOrDefault(x => x.SessionKey == sessionKey);
            if (user == null)
            {
                throw new ArgumentException("Invalid user authentication.");
            }

            user.SessionKey = null;
            this.context.SaveChanges();
        }

        private static string GenerateSessionKey(int userId)
        {
            StringBuilder keyChars = new StringBuilder(50);
            keyChars.Append(userId.ToString());
            while (keyChars.Length < SessionKeyLen)
            {
                int randomCharNum;
                lock (rand)
                {
                    randomCharNum = rand.Next(SessionKeyChars.Length);
                }
                char randomKeyChar = SessionKeyChars[randomCharNum];
                keyChars.Append(randomKeyChar);
            }
            string sessionKey = keyChars.ToString();
            return sessionKey;
        }

        private static void ValidateSessionKey(string sessionKey)
        {
            if (sessionKey.Length != SessionKeyLen || sessionKey.Any(ch => !SessionKeyChars.Contains(ch)))
            {
                throw new ArgumentException("Invalid session key.");
            }
        }
    }
}
